/*
 * Licensed to the ConArtist Development Team (CADT).
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * The CADT licenses this file to you under the Apache License, 
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <httpd.h>
#include <http_config.h>
#include <http_core.h>
#include <http_log.h>
#include <http_main.h>
#include <http_protocol.h>
#include <http_request.h>
#include <util_script.h>
#include <http_connection.h>

#include <apr_lib.h>
#include <apr_strings.h>
#include <apr_md5.h>
#include <apr_uri.h>
#include <apr_strmatch.h>

#include "conartist.h"

module AP_MODULE_DECLARE_DATA conartist_module;

#define SESSION_VER_MAJOR        0
#define SESSION_VER_MINOR        1

typedef struct ca_data {
    struct ca_data *next;     /* pointer to next ca_data structure */
    apr_pool_t *pool;         /* pool we allocate memory from */
    char *configFn;           /* Configuration filename to use */
    char *dirPath;            /* Path we were created from.
                               * NB This is removed from the URI passed
                               *    into ConArtist.
                               */
    apr_size_t dirLen;        /* Length of dirPath (if set) */
    int enabled;              /* Is ConArtist enabled? */
    int activated;            /* Set when the config file has been parsed */
} ca_data_t;
static ca_data_t *configurations = NULL;

#define CONARTIST_HANDLER_NAME "conartist-handler"

/* Cleanup the session. This is called when we destroy the 
 * session_data_t object.
 * This is mainly just a place holder to add any cleanups that
 * may be required. Presently there arent any.
 */
static apr_status_t ca_cleanup(void *dummy)
{
    ca_data_t *sd = (ca_data_t *)dummy;

    /* Insert cleanups here */

    return APR_SUCCESS;
}

/* Add the module to the component description line.
 * Any additional startup tasks should be added here.
 */
static int conartist_init_handler(apr_pool_t *p, apr_pool_t *plog, 
                                  apr_pool_t *ptemp, server_rec *s)
{
    char *verString = NULL;
    apr_status_t rv = caInit(p);
    ca_data_t *c = NULL;

    verString = apr_psprintf(ptemp, "ConArtist %s", caGetVersion());
    ap_add_version_component(p, verString);

ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, "mod_conartist init");

    /* Insert startup tasks here */

    for (c = configurations; c; c = c->next) {
        apr_status_t rv;

        if (!c->enabled)
            continue;
        if (c->activated)
            continue;

        rv = caConfigFileParse(c->configFn);
        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, 
                         "Configuring ConArtist failed");
            ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, 
                         "%s", caLastError());            
            return DECLINED;
        }
        c->activated = 1;
    }

    return OK;
}

static int conartist_fixups(request_rec *r)
{
    ca_data_t *cd = NULL;

    /* Do we need to reject HTTP/0.9 requests? */
    if (r->assbackwards && !r->main)
        return DECLINED;

    cd = (ca_data_t *)ap_get_module_config(r->per_dir_config,
                                           &conartist_module);

    /* If SPARQL hasn't been enabled, decline the kind offer */
    if (! cd->enabled) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, 
                      "ConArtist not enabled, declining");
        return DECLINED;
    }
    if (!cd->activated) {
        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, 
                      "ConArtist is not activated, declining");
        return DECLINED;
    }

    /* If the method isn't GET, then decline the kind offer */
    if (r->method_number != M_GET && r->method_number != M_POST)
        return DECLINED;
 
    r->handler = CONARTIST_HANDLER_NAME;
    return OK;
}

static int conartist_handler(request_rec *r)
{
    ca_data_t *cd = NULL;
    char *uri = r->uri;
    char *buffer = NULL;
    apr_size_t bufSz = 0;
    apr_status_t rv;
    struct caContent ct;
    conn_rec *c = r->connection;

    /* If we haven't been selected, decline to do anything! */
    if (strcmp(r->handler, CONARTIST_HANDLER_NAME) != 0)
        return DECLINED;

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "conartist_handler()");

    cd = (ca_data_t *)ap_get_module_config(r->per_dir_config,
                                           &conartist_module);
    if (!cd)
        return DECLINED;

    if (cd->dirLen) {
        apr_size_t uLen = strlen(r->uri);
        if (uLen > cd->dirLen)
            uri = &r->uri[cd->dirLen];
    }

    ct.pool = r->pool;
    ct.hostname = ap_get_server_name(r);
    ct.port = ap_get_server_port(r);
    ct.uri = uri;
    ct.headers_in = r->headers_in;

    rv = caGetContent(&ct);

    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, "ct.dataLen = %d, filename = %s",
                  ct.dataLen, ct.filename);

    if (rv == APR_SUCCESS) {
        /* Set content type */
        if (ct.contentType)
            ap_set_content_type(r, ct.contentType);
        /* TODO - encoding?? */

        /* TODO - merge headers in */

        /* Provide content! */
        if (ct.dataLen) {
            r->status = HTTP_OK;
            ap_rvputs(r, ct.data, NULL);
            return DONE;
        } else if (ct.filename) {
            apr_file_t *fh;
            apr_bucket *bucket;
            apr_bucket_brigade *bb;
            apr_off_t pos = 0;

            rv = apr_file_open(&fh, ct.filename, APR_READ,
                               APR_OS_DEFAULT, r->pool);
            if (rv != APR_SUCCESS) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                             "file permissions deny server access: %s", 
                             ct.filename);
                return HTTP_FORBIDDEN;
            }

            bb = apr_brigade_create(r->pool, c->bucket_alloc);
            apr_brigade_insert_file(bb, fh, 0, ct.fLen, r->pool);

            bucket = apr_bucket_eos_create(c->bucket_alloc);
            APR_BRIGADE_INSERT_TAIL(bb, bucket);
            rv = ap_pass_brigade(r->output_filters, bb);
            if (rv != APR_SUCCESS) {
                ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                              "mod_conartist: ap_pass_brigade failed "
                              "for file %s", ct.filename);
                return HTTP_INTERNAL_SERVER_ERROR;
            }
            return OK;
        }
    }

    return DECLINED;
}

static ca_data_t *makeCAData(apr_pool_t *p)
{
    ca_data_t *cd = apr_pcalloc(p, sizeof(*cd));
    if (!cd)
        return NULL;
    cd->pool = p;

ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, "makeCAData()");

    /* Append the configuration to our list */
    if (configurations) {
        ca_data_t *c;
        for (c = configurations; c->next; c = c->next)
            ;
        c->next = cd;
    } else
        configurations = cd;

    return cd;
}

static void *conartist_create_config(apr_pool_t *p, server_rec *s)
{
    ca_data_t *sd = makeCAData(p);
    if (!sd)
        return NULL;
    return sd;
}

static void *conartist_dir_config(apr_pool_t *p, char *dir)
{
    ca_data_t *sd = makeCAData(p);
    if (!sd)
        return NULL;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "conartist_dir_config(%s)", dir);
    sd->dirPath = apr_pstrdup(p, dir);
    if (sd->dirPath)
        sd->dirLen = strlen(sd->dirPath);

    return sd;
}

static const command_rec session_cmds[] =
{
    AP_INIT_FLAG("ConArtist", ap_set_flag_slot, 
                 (void *)APR_OFFSETOF(ca_data_t, enabled),
                 RSRC_CONF|ACCESS_CONF,
                 "Use ConArtist [default No]"),
    AP_INIT_TAKE1("ConArtistConfigFile", ap_set_string_slot,
                  (void *)APR_OFFSETOF(ca_data_t, configFn),
                  RSRC_CONF|ACCESS_CONF,
                  "Configuration filename to use"),
    {NULL}
};

static void register_hooks(apr_pool_t *p)
{
    ap_hook_post_config(conartist_init_handler, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_fixups(conartist_fixups, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_handler(conartist_handler, NULL, NULL, APR_HOOK_FIRST);
}

module AP_MODULE_DECLARE_DATA conartist_module =
{
    STANDARD20_MODULE_STUFF,
    conartist_dir_config,       /* create per-directory config structure */
    NULL,                       /* merge per-directory config structures */
    conartist_create_config,    /* create per-server config structure */
    NULL,                       /* merge per-server config structures */
    session_cmds,               /* command apr_table_t */
    register_hooks
};
